#include "LightSource.h"

namespace lib3dw
{
	LightSource::LightSource()
	{
			shadowMapTextureId = new GLuint[1];
			shadowMapTextureId[0] = -1;
			shadowMapUniformId = -1;
			
			position = new GLfloat[4];
			position[0] = -0.3;
			position[1] = -0.4;
			position[2] = 1.0;
			position[3] = 1.0;
			
			ambient = new GLfloat[4];
			ambient[0] = 0.0;
			ambient[1] = 0.0;
			ambient[2] = 0.0;
			ambient[3] = 0.0;
			
			diffuse = new GLfloat[4];
			diffuse[0] = 1.0;
			diffuse[1] = 1.0;
			diffuse[2] = 1.0;
			diffuse[3] = 1.0;
			
			specular = new GLfloat[4];
			specular[0] = 1.0;
			specular[1] = 1.0;
			specular[2] = 1.0;
			specular[3] = 1.0;
						
			lightProjMat = new float[16];
			lightViewMat = new float[16];
			camProjMat = new float[16];
			camViewMat = new float[16];
	}

	LightSource::~LightSource()
	{
		delete[] position;
		delete[] ambient;
		delete[] diffuse;
		delete[] specular;
		delete[] lightProjMat;
		delete[] lightViewMat;
		delete[] camProjMat;
		delete[] camViewMat;
		delete shadowProgram;
		delete shadowMapTextureId;
	}
	
	void LightSource::InitShadowMap(Camera* newCamera, GLSLShaderProgram* newProgram, int newTextureSlot)
	{
		camera = newCamera;
		shadowProgram = newProgram;
		textureSlot = newTextureSlot;
		
		CreateFBODepthTexture();
		glMatrixMode(GL_MODELVIEW);
		SetupCameraMatrices(camera);
		SetupLightMatrices();
		glUseProgram(shadowProgram->GetId());
		
		std::ostringstream s;
		s << textureSlot;
		std::string shadowMapNumber = "shadowMap" + s.str();
		shadowMapUniformId = glGetUniformLocation(shadowProgram->GetId(), shadowMapNumber.c_str());
		glUseProgram(0);
	}
	
	void LightSource::UpdateShadowMap(void (*DrawMethod)())
	{
		RenderFromLightPosition(DrawMethod);
		RenderFromCameraPosition(camera, DrawMethod);
	}

	const GLfloat* LightSource::GetPosition() const
	{
		return position;
	}

	const GLfloat* LightSource::GetDiffuse() const
	{
		return diffuse;
	}

	const GLfloat* LightSource::GetAmbient() const
	{
		return ambient;
	}

	const GLfloat* LightSource::GetSpecular() const
	{
		return specular;
	}

	const GLenum LightSource::GetLightNumber() const
	{
		return  lightNumber;
	}
	
	const GLuint LightSource::GetTextureSlot() const
	{
		return textureSlot;
	}

	LightType LightSource::GetLightType()
	{
		return Black;
	}

	void LightSource::SetPosition(float x, float y, float z, float w)
	{
		position[0] = x;
		position[1] = y;
		position[2] = z;
		position[3] = w;
	}

	void LightSource::SetAmbient(float r, float g, float b, float a)
	{
		ambient[0] = r;
		ambient[1] = g;
		ambient[2] = b;
		ambient[3] = a;
	}

	void LightSource::SetDiffuse(float r, float g, float b, float a)
	{
		diffuse[0] = r;
		diffuse[1] = g;
		diffuse[2] = b;
		diffuse[3] = a;
	}

	void LightSource::SetSpecular(float r, float g, float b, float a)
	{
		specular[0] = r;
		specular[1] = g;
		specular[2] = b;
		specular[3] = a;
	}

	void LightSource::SetLightNumber(GLenum number)
	{
		lightNumber = number;
	}

	void LightSource::CreateFBODepthTexture()
	{
		glGenFramebuffersEXT(1, &fboId);
		glGenTextures(1, &shadowMapTextureId[0]);
		glBindTexture(GL_TEXTURE_2D, shadowMapTextureId[0]);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, GetShadowMapSize(), GetShadowMapSize(), 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL);
		glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE_ARB, GL_INTENSITY);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE);
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER );
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER );
        float borderC[4]; borderC[0]=1;borderC[1]=1;borderC[2]=1;borderC[3]=1;
        glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_BORDER_COLOR, borderC);
	}

	bool LightSource::InvertCameraViewMatrix(float i[16], const float m[16])
	{
		float x= m[12]*m[9]*m[6]*m[3]-
		m[8]*m[13]*m[6]*m[3]-
		m[12]*m[5]*m[10]*m[3]+
		m[4]*m[13]*m[10]*m[3]+
		m[8]*m[5]*m[14]*m[3]-
		m[4]*m[9]*m[14]*m[3]-
		m[12]*m[9]*m[2]*m[7]+
		m[8]*m[13]*m[2]*m[7]+
		m[12]*m[1]*m[10]*m[7]-
		m[0]*m[13]*m[10]*m[7]-
		m[8]*m[1]*m[14]*m[7]+
		m[0]*m[9]*m[14]*m[7]+
		m[12]*m[5]*m[2]*m[11]-
		m[4]*m[13]*m[2]*m[11]-
		m[12]*m[1]*m[6]*m[11]+
		m[0]*m[13]*m[6]*m[11]+
		m[4]*m[1]*m[14]*m[11]-
		m[0]*m[5]*m[14]*m[11]-
		m[8]*m[5]*m[2]*m[15]+
		m[4]*m[9]*m[2]*m[15]+
		m[8]*m[1]*m[6]*m[15]-
		m[0]*m[9]*m[6]*m[15]-
		m[4]*m[1]*m[10]*m[15]+
		m[0]*m[5]*m[10]*m[15];
		
		if (x==0) return false;
		
		i[0]= (-m[13]*m[10]*m[7] +m[9]*m[14]*m[7] +m[13]*m[6]*m[11]
			   -m[5]*m[14]*m[11] -m[9]*m[6]*m[15] +m[5]*m[10]*m[15])/x;
		i[4]= ( m[12]*m[10]*m[7] -m[8]*m[14]*m[7] -m[12]*m[6]*m[11]
				+m[4]*m[14]*m[11] +m[8]*m[6]*m[15] -m[4]*m[10]*m[15])/x;
		i[8]= (-m[12]*m[9]* m[7] +m[8]*m[13]*m[7] +m[12]*m[5]*m[11]
			   -m[4]*m[13]*m[11] -m[8]*m[5]*m[15] +m[4]*m[9]* m[15])/x;
		i[12]=( m[12]*m[9]* m[6] -m[8]*m[13]*m[6] -m[12]*m[5]*m[10]
				+m[4]*m[13]*m[10] +m[8]*m[5]*m[14] -m[4]*m[9]* m[14])/x;
		i[1]= ( m[13]*m[10]*m[3] -m[9]*m[14]*m[3] -m[13]*m[2]*m[11]
				+m[1]*m[14]*m[11] +m[9]*m[2]*m[15] -m[1]*m[10]*m[15])/x;
		i[5]= (-m[12]*m[10]*m[3] +m[8]*m[14]*m[3] +m[12]*m[2]*m[11]
			   -m[0]*m[14]*m[11] -m[8]*m[2]*m[15] +m[0]*m[10]*m[15])/x;
		i[9]= ( m[12]*m[9]* m[3] -m[8]*m[13]*m[3] -m[12]*m[1]*m[11]
				+m[0]*m[13]*m[11] +m[8]*m[1]*m[15] -m[0]*m[9]* m[15])/x;
		i[13]=(-m[12]*m[9]* m[2] +m[8]*m[13]*m[2] +m[12]*m[1]*m[10]
			   -m[0]*m[13]*m[10] -m[8]*m[1]*m[14] +m[0]*m[9]* m[14])/x;
		i[2]= (-m[13]*m[6]* m[3] +m[5]*m[14]*m[3] +m[13]*m[2]*m[7]
			   -m[1]*m[14]*m[7] -m[5]*m[2]*m[15] +m[1]*m[6]* m[15])/x;
		i[6]= ( m[12]*m[6]* m[3] -m[4]*m[14]*m[3] -m[12]*m[2]*m[7]
				+m[0]*m[14]*m[7] +m[4]*m[2]*m[15] -m[0]*m[6]* m[15])/x;
		i[10]=(-m[12]*m[5]* m[3] +m[4]*m[13]*m[3] +m[12]*m[1]*m[7]
			   -m[0]*m[13]*m[7] -m[4]*m[1]*m[15] +m[0]*m[5]* m[15])/x;
		i[14]=( m[12]*m[5]* m[2] -m[4]*m[13]*m[2] -m[12]*m[1]*m[6]
				+m[0]*m[13]*m[6] +m[4]*m[1]*m[14] -m[0]*m[5]* m[14])/x;
		i[3]= ( m[9]* m[6]* m[3] -m[5]*m[10]*m[3] -m[9]* m[2]*m[7]
				+m[1]*m[10]*m[7] +m[5]*m[2]*m[11] -m[1]*m[6]* m[11])/x;
		i[7]= (-m[8]* m[6]* m[3] +m[4]*m[10]*m[3] +m[8]* m[2]*m[7]
			   -m[0]*m[10]*m[7] -m[4]*m[2]*m[11] +m[0]*m[6]* m[11])/x;
		i[11]=( m[8]* m[5]* m[3] -m[4]*m[9]* m[3] -m[8]* m[1]*m[7]
				+m[0]*m[9]* m[7] +m[4]*m[1]*m[11] -m[0]*m[5]* m[11])/x;
		i[15]=(-m[8]* m[5]* m[2] +m[4]*m[9]* m[2] +m[8]* m[1]*m[6]
			   -m[0]*m[9]* m[6] -m[4]*m[1]*m[10] +m[0]*m[5]* m[10])/x;
		
		return true;
	} 

	void LightSource::SetupTextureMatrix()
	{
		glMatrixMode(GL_TEXTURE);
		glLoadIdentity();
		glTranslatef(0.5f, 0.5f, 0.5f);
		glScalef(0.5f, 0.5f, 0.5f);
		glMultMatrixf(lightProjMat);
		glMultMatrixf(lightViewMat);
		float* tmpMat = new float[16];
		InvertCameraViewMatrix(tmpMat, camViewMat);
		glMultMatrixf(tmpMat);
		glMatrixMode(GL_MODELVIEW);
	}

	void LightSource::SetupCameraMatrices(Camera* camera)
	{
		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
		glLoadIdentity();
		gluPerspective(camera->GetAngle(), camera->GetAspect(), camera->GetNear(), camera->GetFar());
		glGetFloatv(GL_MODELVIEW_MATRIX, camProjMat);
		glLoadIdentity();
		gluLookAt(camera->GetEye().x, camera->GetEye().y, camera->GetEye().z,
			camera->GetLook().x, camera->GetLook().y, camera->GetLook().z,
			camera->GetUp().x, camera->GetUp().y, camera->GetUp().z);
		glGetFloatv(GL_MODELVIEW_MATRIX, camViewMat);
		glPopMatrix();
	}

	void LightSource::CheckFramebufferStatus()
	{
		int status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
		switch(status)
		{
			case GL_FRAMEBUFFER_COMPLETE_EXT:
			break;

			case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
			std::cout << "Frame buffer extension not supported.";
			case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
			std::cout << "Incomplete attachments";
			case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
			std::cout << "Incomplete dimensions";
			case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
			std::cout << "Incomplete drw buffer";
			case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
			std::cout << "incomplete formats";
			case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
			std::cout << "incomplete attachments";
			case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
			std::cout << "Incomplete read buffer";

			default:
			std::cout << "Logic error";
		}
	}

	void LightSource::RenderFromLightPosition(void (*DrawMethod)())
	{
		SetupLightMatrices();
		glEnable(GL_TEXTURE_2D);
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, shadowMapTextureId[0], 0);
		glDrawBuffer(GL_NONE);
		glReadBuffer(GL_NONE);
		CheckFramebufferStatus();
		glViewport(0, 0, GetShadowMapSize(), GetShadowMapSize());
		glShadeModel(GL_FLAT);
		glColorMask(false, false, false, false);
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glLoadMatrixf(lightProjMat);
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		glLoadMatrixf(lightViewMat);
		glEnable(GL_POLYGON_OFFSET_FILL);
		glPolygonOffset(0.5f, 1.0f);
		glClear(GL_DEPTH_BUFFER_BIT);
		DrawMethod();
		glDisable(GL_POLYGON_OFFSET_FILL);
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
		glShadeModel(GL_SMOOTH);
		glColorMask(true, true, true, true);
		glViewport(0, 0, glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT));
		glDisable(GL_TEXTURE_2D);
	}

	void LightSource::RenderFromCameraPosition(Camera* camera, void (*DrawMethod)())
	{
		SetupCameraMatrices(camera);
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glLoadMatrixf(camProjMat);
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		glLoadMatrixf(camViewMat);
		glViewport(0, 0, glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT));
		glActiveTexture(GL_TEXTURE0 + 1 + textureSlot);
		SetupTextureMatrix();
	}
	
	GLuint LightSource::GetShadowProgramId()
	{
		return shadowProgram->GetId();
	}
	
	GLuint LightSource::GetShadowProgramUniformTextureUnitId()
	{
		return shadowMapUniformId;
	}

	GLuint LightSource::GetShadowMapTextureId()
	{
		return shadowMapTextureId[0];
	}
}